package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {


    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;


    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private RedisTemplate redisTemplate;


    @Autowired
    //@Qualifier("threadPoolTaskExecutor")
    //@Resource(name = "threadPoolTaskExecutor")
    private Executor threadPoolTaskExecutor;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 专辑上架，将指定专辑ID发布保存到索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        //1.初始化专辑索引库文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        //2.封装专辑及标签信息-远程调用专辑服务获取专辑相关信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumId, "专辑{}不存在", albumId);
            //2.1 将专辑基本信息拷贝
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //2.2 封装专辑标签列表信息
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                //采用Stream流对集合类型进行变更
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(albumAttributeValue -> BeanUtil.copyProperties(albumAttributeValue, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);

        //3.封装专辑分类信息-远程调用专辑服务根据三级分类ID查询分类信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            Long category3Id = albumInfo.getCategory3Id();
            BaseCategoryView categoryView = albumFeignClient.getCategoryView(category3Id).getData();
            Assert.notNull(categoryView, "专辑{}所属分类{}不存在", albumId, category3Id);
            albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
            albumInfoIndex.setCategory3Id(categoryView.getCategory3Id());
        }, threadPoolTaskExecutor);

        //4.封装主播信息-远程调用用户服务根据用户ID查询主播信息
        CompletableFuture<Void> userCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            Long userId = albumInfo.getUserId();
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(userId).getData();
            Assert.notNull(userInfoVo, "专辑{}所属主播{}不存在", albumId, userId);
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);

        //5.封装专辑统计信息，采用随机生成数值，后续改为远程调用专辑服务获取专辑统计信息
        CompletableFuture<Void> statCompletableFuture = CompletableFuture.runAsync(() -> {
            //5.1 随机产生四项统计数值
            int num1 = RandomUtil.randomInt(1000, 2000);
            int num2 = RandomUtil.randomInt(1000, 1500);
            int num3 = RandomUtil.randomInt(500, 1000);
            int num4 = RandomUtil.randomInt(300, 800);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);

            //5.2 基于四项统计数值分别设置不同权重，最终算出专辑热度值
            BigDecimal bigDecimal1 = BigDecimal.valueOf(num1).multiply(new BigDecimal("0.1"));
            BigDecimal bigDecimal2 = BigDecimal.valueOf(num2).multiply(new BigDecimal("0.2"));
            BigDecimal bigDecimal3 = BigDecimal.valueOf(num3).multiply(new BigDecimal("0.3"));
            BigDecimal bigDecimal4 = BigDecimal.valueOf(num4).multiply(new BigDecimal("0.4"));
            double hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4).doubleValue();
            albumInfoIndex.setHotScore(hotScore);
        }, threadPoolTaskExecutor);


        //6. 组合异步任务，等待所有任务都完成才执行下一步
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                statCompletableFuture,
                categoryCompletableFuture,
                userCompletableFuture
        ).join();
        albumInfoIndexRepository.save(albumInfoIndex);

        //7.将专辑标题存入提词索引库
        this.saveSuggestDoc(albumInfoIndex);

        //8.将上架专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumId);
    }


    /**
     * 将专辑索引库中标题存入提词索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggestDoc(AlbumInfoIndex albumInfoIndex) {
        //1.创建提词文档对象
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        //用于汉字自动补全
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //用于汉语拼音自动补全 将汉字转为拼音
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //用于汉语拼音首字母自动补全 将汉字转为拼音首字母
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));

        //2.保存提词文档对象
        suggestIndexRepository.save(suggestIndex);
    }

    /**
     * 搜索关键字自动补全
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.根据用户录入内容检索提词索引库
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient
                    .search(s -> s
                                    .suggest(
                                            s1 -> s1.suggesters("keyWordSuggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").skipDuplicates(true).size(10)))
                                                    .suggesters("keyWordPinyinSuggest", s3 -> s3.prefix(keyword).completion(c -> c.field("keywordPinyin").skipDuplicates(true).size(10)))
                                                    .suggesters("letterSuggest", s3 -> s3.prefix(keyword).completion(c -> c.field("keywordSequence").skipDuplicates(true).size(10)))
                                    )
                            ,
                            SuggestIndex.class);
            //2.解析提词索引库检索结果将结果放入HashSet中  HashSet为何能去重，底层HashMap的key是唯一的,hash不同存入，hashcode相同判断值内容相同，内容不同存入
            Set<String> hashSet = new HashSet<>();
            hashSet.addAll(this.parseSuggestResult(searchResponse, "keyWordSuggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "keyWordPinyinSuggest"));
            hashSet.addAll(this.parseSuggestResult(searchResponse, "letterSuggest"));

            //3.如果结果长度小于10，尝试根据文本进行全文查询补全到10个
            if (hashSet.size() < 10) {
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(s -> s.index(INDEX_NAME).query(q -> q.match(m -> m.field("albumTitle").query(keyword))).size(10).source(s1 -> s1.filter(f -> f.includes("albumTitle"))), AlbumInfoIndex.class);
                List<Hit<AlbumInfoIndex>> hitList = response.hits().hits();
                if (CollectionUtil.isNotEmpty(hitList)) {
                    for (Hit<AlbumInfoIndex> albumInfoIndexHit : hitList) {
                        String albumTitle = albumInfoIndexHit.source().getAlbumTitle();
                        hashSet.add(albumTitle);
                        if (hashSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            //4.如果结果长度大于10最多返回10个
            if (hashSet.size() < 10) {
                return new ArrayList<>(hashSet);
            } else {
                return new ArrayList<>(hashSet).subList(0, 10);
            }
        } catch (IOException e) {
            log.error("[搜索服务]关键字自动补全异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 从建议结果中获取建议词列表
     *
     * @param searchResponse
     * @param suggestName
     * @return
     */
    @Override
    public List<String> parseSuggestResult(SearchResponse<SuggestIndex> searchResponse, String suggestName) {
        List<String> result = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(suggestName);
        if (CollectionUtil.isNotEmpty(suggestionList)) {
            //解析获取自定义建议参数对应建议词结果
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : suggestionList) {
                for (CompletionSuggestOption<SuggestIndex> option : suggestIndexSuggestion.completion().options()) {
                    SuggestIndex suggestIndex = option.source();
                    String title = suggestIndex.getTitle();
                    result.add(title);
                }
            }
        }
        return result;
    }

    /**
     * 统计ES不同分类下不通维度热度较高专辑列表存入Redis缓存
     *
     * @return
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用专辑服务获取一级分类列表，得到所有一级分类ID列表
            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
            Assert.notNull(baseCategory1List, "一级分类列表为空");

            //2.遍历一级分类列表，查询每个一级分类下，对应维度下热度较高专辑top10列表，将结果写入Redis缓存
            for (BaseCategory1 baseCategory1 : baseCategory1List) {
                Long category1Id = baseCategory1.getId();
                //2.0 声明存入Redis中排行榜的key（标识分类）
                String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                //声明绑定Hash结构操作对象 决定操作哪个Hash结构Key
                BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
                //2.1 每个分类下都有固定五种排序维度
                List<String> sortFieldList = Arrays.asList("hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum");
                //2.2 遍历排序维度列表，查询每个维度下top10专辑列表
                for (String sortField : sortFieldList) {
                    //2.2.1 声明存入Redis中排行榜Field（标识排序字段）
                    String filed = sortField;
                    //2.2.2 根据分类一级ID和排序字段查询top10专辑列表
                    SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient
                            .search(s -> s.index(INDEX_NAME)
                                            .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                            .sort(s1 -> s1.field(f -> f.field(sortField).order(SortOrder.Desc)))
                                            .size(10)
                                            .source(s1 -> s1.filter(f -> f.includes("id", "albumTitle", "albumIntro", "payType", "includeTrackCount", "playStatNum", "coverUrl")))
                                    , AlbumInfoIndex.class);
                    //2.3解析ES响应结果将结果存入Redis
                    List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                    if (CollectionUtil.isNotEmpty(hitList)) {
                        List<AlbumInfoIndex> albumInfoIndexList =
                                hitList.stream().map(hit -> hit.source()).collect(Collectors.toList());
                        //2.4 存入Redis
                        //redisTemplate.opsForHash().put(key, filed, albumInfoIndexList);
                        hashOps.put(filed, albumInfoIndexList);
                    }
                }
            }
        } catch (IOException e) {
            log.error("[搜索服务]更新Redis小时排行榜异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据1级分类ID以及排序字段获取TOP10专辑列表
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndex> findRankingList(Long category1Id, String dimension) {
        //1.声明获取Redis中排行榜的key（标识分类）
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        //2.根据Key+Field获取hash结构中Value
        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
        List<AlbumInfoIndex> list = hashOps.get(dimension);
        return list;
    }

    /**
     * 更新专辑统计数据
     *
     * @param trackStatMqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo trackStatMqVo) {
        try {
            Long albumId = trackStatMqVo.getAlbumId();
            elasticsearchClient.update(
                    u -> u.index(INDEX_NAME).id(albumId.toString())
                            .script(s->s.inline(i->i.lang("painless").source("ctx._source.playStatNum += params.increment").params(Map.of("increment",JsonData.of(trackStatMqVo.getCount())))))
                    , AlbumInfoIndex.class
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 将指定专辑从索引库中删除
     *
     * @param albumId
     * @return
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        //删除对应文档的提词记录
        suggestIndexRepository.deleteById(albumId.toString());
    }


    /**
     * 站内专辑搜索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //一、构建用于检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.out.println("本次检索DSL：");
            System.err.println(searchRequest.toString());
            //二、执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            //三、解析ES响应结果
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (IOException e) {
            log.error("[搜索服务]站内搜索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";
    private static final String SUGGEST_INDEX_NAME = "suggestinfo";

    /**
     * 基于入参查询条件封装站内搜索完整DSL语句
     *
     * @param albumIndexQuery 查询条件对象
     * @return 检索请求对象
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1. 创建检索请求构建器对象 作用：封装DSL语句完整请求
        SearchRequest.Builder builder = new SearchRequest.Builder();
        //2.设置检索索引库名称
        builder.index(INDEX_NAME);

        //3.设置查询条件 请求体参数"query"包含三大条件：关键字、分类、标签条件
        String keyword = albumIndexQuery.getKeyword();
        //3.1 创建封装所有查询条件Bool查询对象
        BoolQuery.Builder allConditionQueryBuilder = new BoolQuery.Builder();
        //3.2 设置第一大条件：关键字，包含三个子条件：匹配查询标题或简介，等值查询主播名称
        if (StringUtils.isNotBlank(keyword)) {
            allConditionQueryBuilder.must(m -> m.bool(
                    b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                            .should(s -> s.match(m1 -> m1.field("albumIntro").query(keyword)))
                            .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
            ));
        }
        //3.2 设置第二大条件：分类 精确查询
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allConditionQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allConditionQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allConditionQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }

        //3.2  设置第三大条件：标签 一组标签条件串，标签ID:标签值ID
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            //3.3 每遍历一个标签对象，设置Nested查询对象
            for (String attribute : attributeList) {
                String[] split = attribute.split(":");
                if (split != null && split.length == 2) {
                    allConditionQueryBuilder.filter(
                            f -> f.nested(n -> n.path("attributeValueIndexList").query(
                                            q -> q.bool(
                                                    b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                            .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                            )
                                    )
                            )
                    );
                }
            }
        }

        //3.x 将查询构建器对象转为查询对象
        builder.query(allConditionQueryBuilder.build()._toQuery());
        //3.设置分页 请求体参数"from"和"size"
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);

        //4.设置高亮 当前存在关键字条件，设置请求体参数"highlight"
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", t -> t.preTags("<font style='color:red'>").postTags("</font>")));
        }

        //5.设置排序 设置请求体参数"sort" 前后端约定排序参数值  排序字段:排序方式
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //5.1 获取排序字段
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                String orderFiled = "";
                switch (split[0]) {
                    case "1":
                        orderFiled = "hotScore";
                        break;
                    case "2":
                        orderFiled = "playStatNum";
                        break;
                    case "3":
                        orderFiled = "createTime";
                        break;
                }
                String finalOrderFiled = orderFiled;
                //5.2 获取排序方式
                SortOrder sortOrder = split[1].equals("asc") ? SortOrder.Asc : SortOrder.Desc;
                builder.sort(s -> s.field(f -> f.field(finalOrderFiled).order(sortOrder)));
            }
        }

        //6.指定响应字段 设置请求体参数"_source"
        builder.source(s -> s.filter(f -> f.includes("id", "albumTitle", "albumIntro", "payType", "includeTrackCount", "playStatNum", "coverUrl")));

        //7.基于将构建器对象得到检索请求对象
        return builder.build();
    }

    /**
     * 解析ES检索结果，封装VO对象响应客户端
     *
     * @param searchResponse  ES响应结果对象
     * @param albumIndexQuery 查询条件对象
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.构建检索响应VO对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.设置VO对象中分页相关信息
        //2.1 获取查询条件中页码、页大小
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.2 解析ES响应结果获取总记录数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        //2.3 计算总页码数
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        //2.4 封装vo分页相关属性
        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        vo.setTotal(total);
        vo.setTotalPages(totalPages);

        //3.设置VO对象中检索到专辑列表-处理高亮
        List<Hit<AlbumInfoIndex>> hitList = hits.hits();
        if (CollectionUtil.isNotEmpty(hitList)) {
            List<AlbumInfoIndexVo> infoIndexVoList = hitList.stream()
                    .map(hit -> {
                        //3.1 获取原始文档内容
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        //3.2 获取高亮内容
                        Map<String, List<String>> highlightMap = hit.highlight();
                        if (CollectionUtil.isNotEmpty(highlightMap)) {
                            String albumTitleHighlight = highlightMap.get("albumTitle").get(0);
                            albumInfoIndex.setAlbumTitle(albumTitleHighlight);
                        }
                        //3.3 将索引库对象转为VO对象
                        return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
                    }).collect(Collectors.toList());
            vo.setList(infoIndexVoList);
        }
        //4.响应vo
        return vo;
    }

    /**
     * 获取1级分类置顶7个三级分类下包含热度最高6个专辑列表
     *
     * @param category1Id 1级分类ID
     * @return
     */
    @Override
    public List<Map<String, Object>> getTop6(Long category1Id) {
        try {
            //1.远程调用专辑服务获取指定1级分类下置顶7个三级分类
            List<BaseCategory3> baseCategory3List = albumFeignClient.getTop7BaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "获取置顶3级分类失败");

            //1.1 通过Stream流获取7个三级分类ID列表
            //List<Long> baseCategory3IdsList = baseCategory3List.stream()
            //        .map(BaseCategory3::getId).collect(Collectors.toList());

            //1.2 通过stream将集合泛型从BaseCategory3转为FieldValue(封装三级分类ID)
            List<FieldValue> fieldValueList = baseCategory3List
                    .stream()
                    .map(c3 -> FieldValue.of(c3.getId()))
                    .collect(Collectors.toList());

            //1.3 封装结果时候需要根据3级分类ID获取三级分类对象，将三级分类集合转为Map key:3级分类ID value:3级分类对象
            Map<Long, BaseCategory3> category3Map =
                    baseCategory3List
                            .stream()
                            .collect(Collectors.toMap(BaseCategory3::getId, c3 -> c3));

            //2.检索ES获取置顶3级分类下包含热度最高的6个专辑
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(INDEX_NAME)
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .size(0)
                            .aggregations(
                                    "category3Agg", a -> a.terms(t -> t.field("category3Id").size(10))
                                            .aggregations("top6", a1 -> a1.topHits(
                                                            t -> t.sort(sort -> sort.field(f -> f.field("hotScore").order(SortOrder.Desc)))
                                                                    .size(6)
                                                                    .source(s1 -> s1.filter(f -> f.includes("id", "category1Id", "category2Id", "category3Id", "albumTitle", "albumIntro", "payType", "includeTrackCount", "playStatNum", "coverUrl")))
                                                    )
                                            )
                            )
                    , AlbumInfoIndex.class
            );
            //3.解析ES响应结果，封装置顶分类热门专辑Map对象，将Map对象封装为集合
            //3.1 从响应对象中获取"category3Agg"集合结果对象
            LongTermsAggregate category3Agg = searchResponse.aggregations().get("category3Agg").lterms();
            //3.2 从聚合结果对象中获取"buckets"集合对象
            List<LongTermsBucket> categoryBuckets = category3Agg.buckets().array();
            if (CollectionUtil.isNotEmpty(categoryBuckets)) {
                //3.3 遍历bucket集合，获取每个bucket对象中，获取三级分类ID及该分类下热门专辑 每循环一次就需要构建置顶分类热门专辑Map对象
                List<Map<String, Object>> list = categoryBuckets.stream().map(categoryBucket -> {
                    //3.3.1 获取三级分类ID
                    long topCategory3Id = categoryBucket.key();
                    //3.3.2 进一步下钻获取top子聚合
                    TopHitsAggregate top6Agg = categoryBucket.aggregations().get("top6").topHits();
                    //3.3.3 获取子聚合中热门专辑
                    List<Hit<JsonData>> hitList = top6Agg.hits().hits();
                    if (CollectionUtil.isNotEmpty(hitList)) {
                        //3.3.4 遍历热门专辑返回专辑索引库对象
                        List<AlbumInfoIndex> top6AlbumList = hitList.stream()
                                .map(hit -> {
                                    String hitSourceJsonStr = hit.source().toString();
                                    return JSON.parseObject(hitSourceJsonStr, AlbumInfoIndex.class);
                                }).collect(Collectors.toList());
                        //3.4 构建置顶分类热门专辑Map对象
                        Map<String, Object> map = new HashMap<>();
                        map.put("baseCategory3", category3Map.get(topCategory3Id));  //如何根据三级分类ID获取分类对象
                        map.put("list", top6AlbumList);
                        return map;
                    }
                    return null;
                }).collect(Collectors.toList());
                return list;
            }
        } catch (IOException e) {
            log.error("[搜索服务]热门专辑检索异常：{}", e);
            throw new RuntimeException(e);
        }
        return null;
    }


    public static void main(String[] args) {
        //Arrys.asList创建集合不可变集合
        //List<Integer> list = Arrays.asList(1, 2, 3);
        //System.out.println(list);
        List<Long> list = new ArrayList<>();
        list.add(1L);
        list.add(2L);
        //List<Long> collect = list.stream().filter(i -> i >= 2).collect(Collectors.toList());
        //stream中toList 创建集合不可变集合
        List<Long> collect = list.stream().filter(i -> i >= 2).toList();
        collect.add(5L);
        System.out.println(collect);

    }

}
